Skip to content

fix(datastore): preserve staged deletes across Merge composition#1044

Merged
giogam merged 3 commits into
mainfrom
CLD-2758/memory-data-store-merge-silently-dropping-staged-deletes-across-composition
Jun 12, 2026
Merged

fix(datastore): preserve staged deletes across Merge composition#1044
giogam merged 3 commits into
mainfrom
CLD-2758/memory-data-store-merge-silently-dropping-staged-deletes-across-composition

Conversation

@giogam

@giogam giogam commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

MemoryDataStore.Merge propagated src.DeletedRemoteKeys by calling dst.<Store>.Delete(key), which removed the record from dst.Records but never appended the key to dst.DeletedRemoteKeys. Chained Merges (operation and changeset composition) consumed the prior step's staged deletes at every hop; both the JSON file and catalog persistence backends drive remote deletes off DeletedRemoteKeys, so any delete that passed through more than one Merge silently never reached the remote.

Each of the three propagation blocks (AddressRef, ChainMetadata, ContractMetadata) now calls dst.<Store>.RemoteDelete(key) followed by dst.<Store>.Delete(key) with the per-store NotFound sentinel tolerated. Delete intent now survives chained Merges, and Merge is idempotent on repeated calls with the same source.

Behavior change: Merge previously returned ErrNotFound when the source staged a delete for a key not present in dst.Records; it now succeeds and stages the delete on dst. This aligns observable behavior with the RemoteDelete docstring, which already advertised staging deletes for records not held locally. Three existing tests in memory_datastore_test.go are inverted accordingly; new tests cover chained-merge preservation, idempotency, and the precedence of source-live records over destination-staged deletes.

MemoryDataStore.Merge propagated src.DeletedRemoteKeys by calling
dst.<Store>.Delete(key), which removed the record from dst.Records
but never appended the key to dst.DeletedRemoteKeys. Chained Merges
(operation and changeset composition) consumed the prior step's
staged deletes at every hop; both the JSON file and catalog
persistence backends drive remote deletes off DeletedRemoteKeys, so
any delete that passed through more than one Merge silently never
reached the remote.

Each of the three propagation blocks (AddressRef, ChainMetadata,
ContractMetadata) now calls dst.<Store>.RemoteDelete(key) followed
by dst.<Store>.Delete(key) with the per-store NotFound sentinel
tolerated. Delete intent now survives chained Merges, and Merge is
idempotent on repeated calls with the same source.

Behavior change: Merge previously returned Err<X>NotFound when the
source staged a delete for a key not present in dst.Records; it now
succeeds and stages the delete on dst. This aligns observable
behavior with the RemoteDelete docstring, which already advertised
staging deletes for records not held locally. Three existing tests
in memory_datastore_test.go are inverted accordingly; new tests
cover chained-merge preservation, idempotency, and the precedence
of source-live records over destination-staged deletes.
@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0e33953

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
chainlink-deployments-framework Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Fixes the datastore to ensure that staged deletes are preserved during merge composition.
@giogam giogam marked this pull request as ready for review June 11, 2026 15:47
@giogam giogam requested a review from a team as a code owner June 11, 2026 15:47
Comment thread datastore/memory_datastore.go Outdated
Comment on lines +80 to +86
// Precedence: a live record in other.<Store>.Records overrides a staged delete in
// s.<Store>.DeletedRemoteKeys for the same key. This is a side-effect of Upsert,
// which clears the key from DeletedRemoteKeys whenever a record is upserted (so a
// direct `RemoteDelete(k); Upsert(rec-with-k)` cancels the staged delete on the
// same store). Staged deletes are only "sticky" on the source side. Callers that
// need a destination-side staged delete to survive Merge must ensure the source
// either omits the live record or stages the delete itself.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

similar here too? Do users need to know all this before using it? I find readin this kind of overwhelming :D

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this context is important for explaining the behavior of Merge(), as some aspects of its implementation can otherwise appear opaque or difficult to understand.

ajaskolski
ajaskolski previously approved these changes Jun 12, 2026
@giogam giogam dismissed stale reviews from ajaskolski and graham-chainlink via 0e33953 June 12, 2026 10:59
@cl-sonarqube-production

Copy link
Copy Markdown

Quality Gate failed Quality Gate failed

Failed conditions
66.7% Coverage on New Code (required ≥ 80%)
5.56% Technical Debt Ratio on New Code (required ≤ 4%)
B Maintainability Rating on New Code (required ≥ A)

See analysis details on SonarQube

Catch issues before they fail your Quality Gate with our IDE extension SonarQube IDE SonarQube IDE

@giogam giogam added this pull request to the merge queue Jun 12, 2026
Merged via the queue into main with commit f9e7309 Jun 12, 2026
28 of 29 checks passed
@giogam giogam deleted the CLD-2758/memory-data-store-merge-silently-dropping-staged-deletes-across-composition branch June 12, 2026 12:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants